#pragma once
#include <atlutil.h>


template < typename T, typename Traits = DefaultThreadTraits >
class CTimerClient : public IWorkerThreadClient
{
public:

	typedef HRESULT (T::*EXECUTE_METHOD)(DWORD_PTR);
	typedef CWorkerThread<Traits> CThread;

protected:

	CThread *       m_pThread;
	T *             m_pObject;
	EXECUTE_METHOD  m_pMethod;

	HANDLE m_hEvent;

public:

	CTimerClient(void) : 
	  m_pThread(NULL), m_pObject(NULL), m_pMethod(NULL), 
	  m_hEvent(NULL)
	{}

	~CTimerClient(void)
	{
		Disconnect();
		ResetExecutive();
	}

public:

	inline void SetExecutive(T * pObject, EXECUTE_METHOD pMethod) 
	{
		ATLASSERT( !m_pThread );
		m_pObject = pObject;
		m_pMethod = pMethod;
	}

	inline void ResetExecutive(void) 
	{
		ATLASSERT( !m_pThread );
		m_pObject = NULL;
		m_pMethod = NULL;
	}

private: // IWorkerThreadClient

	friend CThread;

	virtual HRESULT Execute(DWORD_PTR dwParam, HANDLE hEvent)
	{
		ATLASSERT( hEvent == m_hEvent );
		ATLASSERT( m_hEvent );

		::ResetEvent(m_hEvent);

		HRESULT hResult = S_OK;

		if ( m_pObject )
			hResult = (m_pObject->*m_pMethod)(dwParam);

		return hResult;
	}

	virtual HRESULT CloseHandle(HANDLE hEvent)
	{
		ATLASSERT( hEvent == m_hEvent );

		if ( m_hEvent )
			::CloseHandle(m_hEvent);

		m_hEvent = NULL;

		return S_OK;
	}

public:

	inline bool IsConnected(void) const { return m_pThread != NULL; }
	inline CThread * GetThread(void) const { return m_pThread; }

	inline HRESULT Connect(CThread * pThread, DWORD dwInterval, DWORD_PTR dwParam = 0)
	{
		Disconnect();

		if ( !pThread )
			return S_FALSE;

		m_pThread = pThread;

		const HRESULT hResult = m_pThread->AddTimer(dwInterval, this, dwParam, &m_hEvent);

		if ( FAILED(hResult) )
			Disconnect();

		return hResult;
	}

	inline void Disconnect(void)
	{
		if ( m_hEvent && m_pThread )
			m_pThread->RemoveHandle(m_hEvent);

		m_pThread = NULL;

		if ( m_hEvent )
			CloseHandle(m_hEvent);
	}

};


template < typename T, typename Traits = DefaultThreadTraits >
class CEventClient : public IWorkerThreadClient
{
public:

	typedef HRESULT (T::*EXECUTE_METHOD)(DWORD_PTR);
	typedef CWorkerThread<Traits> CThread;

protected:

	CThread *       m_pThread;
	T *             m_pObject;
	EXECUTE_METHOD  m_pMethod;

	HANDLE m_hEvent;
	bool   m_bEventOwner;

public:

	CEventClient(void) : 
	  m_pThread(NULL), m_pObject(NULL), m_pMethod(NULL), 
	  m_hEvent(NULL), m_bEventOwner(false)
	{}

	~CEventClient(void)
	{
		Disconnect();
		ResetExecutive();
	}

public:

	inline void SetExecutive(T * pObject, EXECUTE_METHOD pMethod) 
	{
		ATLASSERT( !m_pThread );
		m_pObject = pObject;
		m_pMethod = pMethod;
	}

	inline void ResetExecutive(void) 
	{
		ATLASSERT( !m_pThread );
		m_pObject = NULL;
		m_pMethod = NULL;
	}

private: // IWorkerThreadClient

	friend CThread;

	virtual HRESULT Execute(DWORD_PTR dwParam, HANDLE hEvent)
	{
		ATLASSERT( hEvent == m_hEvent );
		ATLASSERT( m_hEvent );

		::ResetEvent(m_hEvent);

		HRESULT hResult = S_OK;

		if ( m_pObject )
			hResult = (m_pObject->*m_pMethod)(dwParam);

		return hResult;
	}

	virtual HRESULT CloseHandle(HANDLE hEvent)
	{
		ATLASSERT( hEvent == m_hEvent );

		if ( m_bEventOwner && m_hEvent )
			::CloseHandle(m_hEvent);

		m_bEventOwner = false;
		m_hEvent = NULL;

		return S_OK;
	}

public:

	inline bool IsConnected(void) const { return m_pThread != NULL; }
	inline CThread * GetThread(void) const { return m_pThread; }

	inline HRESULT Connect(CThread * pThread, HANDLE hNewEvent = NULL, DWORD_PTR dwParam = 0)
	{
		Disconnect();

		if ( !pThread )
			return S_FALSE;

		if ( hNewEvent )
		{
			m_hEvent = hNewEvent;
			m_bEventOwner = false;
		}
		else
		{
			m_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

			if ( !m_hEvent )
				return HRESULT_FROM_WIN32(::GetLastError());

			m_bEventOwner = true;
		}
		
		m_pThread = pThread;

		const HRESULT hResult = m_pThread->AddHandle(m_hEvent, this, dwParam);

		if ( FAILED(hResult) )
			Disconnect();

		return hResult;
	}

	inline void Disconnect(void)
	{
		if ( m_hEvent && m_pThread )
			m_pThread->RemoveHandle(m_hEvent);

		m_pThread = NULL;

		if ( m_hEvent )
			CloseHandle(m_hEvent);
	}

	inline void Call(void)
	{
		::SetEvent(m_hEvent);
	}

};


template < typename T, typename A, typename Traits = DefaultThreadTraits >
class CEventClientEx : public IWorkerThreadClient
{
public:

	typedef HRESULT (T::*EXECUTE_METHOD)(DWORD_PTR, A);
	typedef CWorkerThread<Traits> CThread;

protected:

	CThread *       m_pThread;
	T *             m_pObject;
	EXECUTE_METHOD  m_pMethod;

	HANDLE m_hEvent;
	bool   m_bEventOwner;

	volatile A              m_arg;
	volatile bool           m_bTransfer;
	CComAutoCriticalSection m_cs;

public:

	CEventClientEx(void) : 
	  m_pThread(NULL), m_pObject(NULL), m_pMethod(NULL), 
	  m_hEvent(NULL), m_bEventOwner(false), 
	  m_cs(), m_arg(), m_bTransfer(false)
	{}

	~CEventClientEx(void)
	{
		Disconnect();
		ResetExecutive();
	}

public:

	inline void SetExecutive(T * pObject, EXECUTE_METHOD pMethod) 
	{
		ATLASSERT( !m_pThread );
		m_pObject = pObject;
		m_pMethod = pMethod;
	}

	inline void ResetExecutive(void) 
	{
		ATLASSERT( !m_pThread );
		m_pObject = NULL;
		m_pMethod = NULL;
	}

private: // IWorkerThreadClient

	friend CThread;

	virtual HRESULT Execute(DWORD_PTR dwParam, HANDLE hEvent)
	{
		ATLASSERT( hEvent == m_hEvent );
		ATLASSERT( m_hEvent );

		::ResetEvent(m_hEvent);

		HRESULT hResult = S_OK;

		if ( m_pObject )
			hResult = (m_pObject->*m_pMethod)(dwParam, m_arg);

		m_bTransfer = false;
		m_cs.Unlock();

		return hResult;
	}

	virtual HRESULT CloseHandle(HANDLE hEvent)
	{
		ATLASSERT( hEvent == m_hEvent );

		if ( m_bEventOwner && m_hEvent )
			::CloseHandle(m_hEvent);

		m_bEventOwner = false;
		m_hEvent = NULL;

		if ( m_bTransfer )
		{
			m_bTransfer = false;
			m_cs.Unlock();
		}

		return S_OK;
	}

public:

	inline bool IsConnected(void) const { return m_pThread != NULL; }
	inline CThread * GetThread(void) const { return m_pThread; }

	inline HRESULT Connect(CThread * pThread, HANDLE hNewEvent = NULL, DWORD_PTR dwParam = 0)
	{
		Disconnect();

		if ( !pThread )
			return S_FALSE;
		
		if ( hNewEvent )
		{
			m_hEvent = hNewEvent;
			m_bEventOwner = false;
		}
		else
		{
			m_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

			if ( !m_hEvent )
				return HRESULT_FROM_WIN32(::GetLastError());

			m_bEventOwner = true;
		}
		
		m_pThread = pThread;

		const HRESULT hResult = m_pThread->AddHandle(m_hEvent, this, dwParam);

		if ( FAILED(hResult) )
			Disconnect();

		return hResult;
	}

	inline void Disconnect(void)
	{
		if ( m_hEvent && m_pThread )
			m_pThread->RemoveHandle(m_hEvent);

		m_pThread = NULL;

		if ( m_hEvent )
			CloseHandle(m_hEvent);

	}

	inline void Call(const A & arg)
	{
		m_cs.Lock();
		m_bTransfer = true;
		m_arg = arg;
		::SetEvent(m_hEvent);
	}

};
